import argparse
import logging
import pickle
import time
import traceback
import random

from redis import Redis
from wrenchbox.logging import setup_log

from ourblockchain.blockchain import Block
from ourblockchain.blockchain import superID


class Builder:
    def __init__(self, url, dbNum):
        self.db = Redis(host=url, db=dbNum)

    def mining(self, transaction):
        # 简单的POW机制，尽量避免多个节点同时将pool打包成block
        goal = transaction.amount

        temp = random.randint(1, 200000000)
        # 随机到传入的交易记录的金额时完成mining
        while temp != goal:
            temp = random.randint(1, 200000000)

    def run(self, k_time, cool_down):
        while True:
            pool = pickle.loads(self.db.get('pool'))
            if len(pool) >= 2:
                tList = [i.t for i in pool]
                t_min = min(tList)
                t_max = max(tList)
                logging.debug("t_min = %s", t_min)
                logging.debug("t_max = %s", t_max)
                if t_max - t_min >= k_time * 60:
                    logging.debug("pool is ready to pack, mining for the right to pack...")
                    # 先将pool中的transaction按时间升序排序
                    poolList = list(pool)
                    # sorted(pool, key=lambda item: item.t)
                    poolList.sort(key=lambda item: item.t)
                    # 再将第一个transaction作为mining参数
                    self.mining(poolList[0])
                    logging.debug("mining is done, begin to pack")

                    # mining后，先看看最新的ledger有没有被抢先了
                    ledger = pickle.loads(self.db.get('ledger'))
                    give_up_flag = False
                    for transaction in poolList:
                        if ledger.find(transaction):
                            give_up_flag = True
                            pool.remove(transaction)  # 已经被人写入了，该item就删除了
                    if give_up_flag:
                        logging.debug("block taken, give up packing")
                        continue  # 重新准备build

                    self.db.set('pool', pickle.dumps(set([])))
                    logging.info("Pool is cleared.")
                    logging.info("Packing %d transactions...", len(pool))

                    block = Block()
                    for transaction in poolList:
                        if transaction.content.purchase_type == 'setPrice'\
                                or ledger.balance(transaction.sender) >= transaction.amount:
                            # 余额没问题后，要在检查是否存在一个区块中出现对同一url的多次own类型购买现象
                            if block.isDoubleOwn(transaction):
                                logging.warning("Dropped transaction due to double own one url in current block: %s", transaction)
                            else:
                                block.add(transaction)
                        else:
                            logging.warning("Dropped transaction due to not enough balance: %s", transaction)
                    logging.info("Block is created with %d transactions.", len(block.items))

                    if len(block.items):
                        try:
                            block.validate()
                        except AssertionError:
                            logging.error('Block is invalid and dropped')
                            if args.debug:
                                traceback.print_exc()
                        else:
                            ledger.add(block)

                            try:
                                ledger.validate()
                            except AssertionError:
                                logging.error('Ledger is invalid and dropped')
                                if args.debug:
                                    traceback.print_exc()
                            else:
                                self.db.set('ledger', pickle.dumps(ledger))
                                logging.info('Block is added to the blockchain.')
                else:
                    logging.debug('records show up too soon, require them separated by %d min', k_time)
            else:
                logging.debug('Currently %d records, require >=2 records to compare time', len(pool))
            time.sleep(cool_down)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--debug',
        action='store_true', default=False,
        help='show debug information'
    )
    parser.add_argument(
        '-r', '--redis',
        type=str, default='localhost',
        help='redis database file, default: localhost'
    )
    parser.add_argument(
        '--dbNum',
        type=int, default=0,
        help='redis sub database number, default: 0'
    )
    parser.add_argument(
        '-k', '--time',
        type=int, default=3,
        help='# of minimum records separated time * min, default: 3'
    )
    parser.add_argument(
        '-t', '--sleep',
        type=int, default=3,
        help='refresh rate in seconds, default: 3'
    )
    args, _ = parser.parse_known_args()
    print(args)
    setup_log(level=logging.DEBUG if args.debug else logging.INFO)
    Builder(args.redis, args.dbNum).run(args.time, args.sleep)
